/*
 * Texture.cpp: implementation of the Texture class.
 */

#include "Texture.h"
#include <math.h>
#include <stdlib.h>
#include <gl\gl.h>
#include <gl\glu.h>

/***********************************************************
 *   Abstract base class for dynamic generated texture.    *
 ***********************************************************/

// Construction
Texture::Texture()
{
	m_width=m_height=m_texsize=0;
	m_data=NULL;
}
// Destruction
Texture::~Texture()
{
	if (m_data)
		delete []m_data;
}

// Initialization.
BOOL Texture::Init(int wdt, int hgt)
{
	if (wdt<=64) m_width=64;
	else if (wdt<=128) m_width=128;
	else m_width=256;

	if (hgt<=64) m_height=64;
	else if (hgt<=128) m_height=128;
	else m_height=256;

	m_texsize=m_width*m_height*3;	// RGB occupy 3 bytes

	// allocate memory for texture data buffer.
	m_data=new BYTE[m_texsize];

	// check its validity.
	if (NULL==m_data)
		return FALSE;

	ZeroMemory(m_data, m_texsize); 
	return TRUE;
}

// Set current texture with specified filter.
void Texture::SetTexture(TEX_FILTER filter)
{
	switch (filter)
	{
	case MIPMAPPING:
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); 
		gluBuild2DMipmaps(GL_TEXTURE_2D, 3, m_width, m_height, GL_RGB, GL_UNSIGNED_BYTE, m_data);
		return;
	case NEAREST:
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		break;
	case LINEAR:
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		break;
	}
	glTexImage2D(GL_TEXTURE_2D, 0, 3, m_width, m_height, 0, GL_RGB, GL_UNSIGNED_BYTE, m_data);
}


/***********************************************************
 * texture class which fill every texel with random color. *
 ***********************************************************/

// Fill the texture with random color for every texel.
void RandomPointTexture::RenderTexture()
{
	BYTE col;

	srand(GetTickCount());
	for (int i=0; i<m_texsize; ++i)
	{
		col=rand()&255;
		m_data[i]=col;
	}
}


/**********************************************************
 *              Simple plasma texture class.              *
 **********************************************************/

// Construction
PlasmaTexture::PlasmaTexture()
{
	m_plasma1=NULL;
	m_plasma2=NULL;
}

//Destruction
PlasmaTexture::~PlasmaTexture()
{
	if (NULL==m_plasma1)
		delete []m_plasma1;
	if (NULL==m_plasma2)
		delete []m_plasma2;
}

// Initialization.
BOOL PlasmaTexture::Init(int wdt, int hgt)
{
	if (!Texture::Init(wdt, hgt))
		return FALSE;

	// allocate memory for plasma1 buffer.
	m_plasma1=new BYTE[m_width*m_height*4];	// plasma buffer will 4 times bigger than texture.
	// check it.
	if (NULL==m_plasma1)
		return FALSE;

	// allocate memory for plasma2 buffer.
	m_plasma2=new BYTE[m_width*m_height*4]; // plasma buffer will 4 times bigger than texture.
	// check it.
	if (NULL==m_plasma2)
		return FALSE;

	// initializate plasma buffers.
	InitPlasma();

	return TRUE;
}

// Render a plasma texture.
void PlasmaTexture::RenderTexture()
{
	// half size of texture's width and height.
	static int half_width =m_width/2;
	static int half_height=m_height/2;

	// the following lines to determine a movement on every plasma.
	// method from Alex Champandard's tutorial "The art of demomaking"
	// URL: http://www.flipcode.com/demomaking/
	// "09/13/99 - Issue 04 - Per Pixel Control"

	int x1, y1;	// top-left point in m_plasma1 buffer.
	int x2, y2; // top-left point in m_plasma2 buffer.
	int offs1, offs2, dst;
	BYTE col;
	double factor=(double)(GetTickCount()>>4);

	x1=half_width  + (int)((half_width-1)  * sin(factor/128)  );
	x2=half_width  + (int)((half_width-1)  * cos(factor/-74)  );
	y1=half_height + (int)((half_height-1) * cos(factor/-113) );
	y2=half_height + (int)((half_height-1) * sin(factor/33)   );

	offs1=y1*m_width*2+x1;
	offs2=y2*m_width*2+x2;

	// update custom palette.
	UpdatePalette();

	// do it.
	dst=0;
	for (int y=0; y<m_height; ++y)
	{
		for (int x=0; x<m_width; ++x)
		{
			col=m_plasma1[offs1]+m_plasma1[offs2]+
				m_plasma2[offs1]+m_plasma2[offs2];

			// because we working under RGB mode, so we just select color
			// from our custom palette, and set corresponding RGB channel
			// to texture buffer.
			m_data[dst  ]=m_palette[col].r;
			m_data[dst+1]=m_palette[col].g;
			m_data[dst+2]=m_palette[col].b;

			++offs1; ++offs2; dst+=3;
		}
		offs1+=m_width;	offs2+=m_width;
	}
}

// Do something with our custom palette.
void PlasmaTexture::UpdatePalette()
{
	double factor=(double)GetTickCount();
	for (int i=0; i<256; ++i)
	{
		m_palette[i].r=(BYTE)(128+127*sin(i*PI/64+factor/133));
		m_palette[i].g=(BYTE)(128-127*cos(i*PI/128+factor/61));
		m_palette[i].b=(BYTE)(128+127*sin(i*PI/64+factor/72));
	}
}

// Prepare plasma buffers.
void PlasmaTexture::InitPlasma()
{
	int width=m_width*2;
	int height=m_height*2;

	int offs=0;
	for (int y=0; y<height; ++y)
	{
		for (int x=0; x<width; ++x)
		{
			m_plasma1[offs]=(BYTE)(128+127*sin((double)hypot(x, m_height-y)/48));
			m_plasma2[offs]=(BYTE)(128+127*sin((float)x/(37+15*cos((float)y/74)))
										  *cos((float)y/(31-21*sin((float)x/57))));
			++offs;
		}
	}
}


/**********************************************************
 *                  Fire texture class.                   *
 **********************************************************/

// Construction
FireTexture::FireTexture()
{
	m_fire1=NULL;
	m_fire2=NULL;

	m_cxBuf=m_cyBuf=0;
}

//Destruction
FireTexture::~FireTexture()
{
	if (NULL==m_fire1)
		delete []m_fire1;
	if (NULL==m_fire2)
		delete []m_fire2;
}

// Initialization.
BOOL FireTexture::Init(int wdt, int hgt)
{
	int bufsize;

	if (!Texture::Init(wdt, hgt))
		return FALSE;

	m_cxBuf=m_width;
	m_cyBuf=m_height+3;	// we stay 3 lines to put random points.

	bufsize=m_cxBuf*m_cyBuf;

	// allocate memory for fire buffers.
	m_fire1=new BYTE[bufsize];
	// check it.
	if (NULL==m_fire1)
		return FALSE;
	memset(m_fire1, 0, bufsize);

	m_fire2=new BYTE[bufsize];
	// check it.
	if (NULL==m_fire2)
		return FALSE;
	memset(m_fire2, 0, bufsize);

	InitPalette();
	return TRUE;
}

// Render a fire texture.
void FireTexture::RenderTexture()
{
	BYTE col;
	int dst=0, offs=0;
	int size=m_width*m_height;

	PutRndPoints();
	BurnBuf();

	for (int i=0; i<size; ++i)
	{
		col=m_fire2[offs];
		m_data[ dst ]=m_palette[col].r;
		m_data[dst+1]=m_palette[col].g;
		m_data[dst+2]=m_palette[col].b;
		dst+=3; ++offs;
	}
	SwapBufPointers();
}

// Prepare custom palette.
void FireTexture::InitPalette()
{
	ShadePalette(  0,  64,   0,   0,   0, 255,  51,  36);
	ShadePalette( 64,  96, 255,  51,  36, 255, 189,  51);
	ShadePalette( 96, 128, 255, 189,  51, 255, 237,  51);
	ShadePalette(128, 196, 255, 237,  51, 100, 100, 189);
	ShadePalette(196, 255, 100, 100, 189, 255, 231,  51);
}

// Set custom palette with begin and end index.
void FireTexture::ShadePalette(BYTE beg, BYTE end,
						   BYTE r1,  BYTE g1,  BYTE b1,
						   BYTE r2,  BYTE g2,  BYTE b2)
{
	if (beg>=end) return;

	double k, interval=(double)(end-beg);

	for (BYTE i=beg; i<end; ++i)
	{
		k=(double)(i-beg)/interval;
		m_palette[i].r=(BYTE)(r1+(r2-r1)*k);
		m_palette[i].g=(BYTE)(g1+(g2-g1)*k);
		m_palette[i].b=(BYTE)(b1+(b2-b1)*k);
	}
}

// Put random spots on buffer.
void FireTexture::PutRndPoints()
{
	int x, y, loop, MaxPoints, MinPoints;

	MinPoints=m_width>>2;
	MaxPoints=MinPoints+(rand()&(m_width-1)); // max number random spots.
	for (loop=MinPoints; loop<MaxPoints; ++loop)
	{
		// random spots.
		x=rand()%m_width;
		y=m_height+(rand()%3);
		m_fire1[y*m_width+x]=rand()&255;
	}

	// draw random spark.
	x=rand()%m_width;
	y=(m_height>>1)+rand()%(m_height>>1);
	m_fire1[y*m_width+x]=rand()&255;
}

// Let the fire burn!
void FireTexture::BurnBuf()
{	
	int  offs=0, height=m_cyBuf-3, width=m_cxBuf-1;
	BYTE col;

	// the following lines to burn the fire,
	// method from Alex Champandard's tutorial "The art of demomaking"
	// URL: http://www.flipcode.com/demomaking/
	// "09/20/99 - Issue 05 - Filters"
	for (int y=0; y<height; ++y)
	{
		m_fire2[offs]=0;
		++offs;
		for (int x=1; x<width; ++x)
		{
			col=(m_fire1[     offs-1     ]+                        m_fire1[     offs+1     ]
				+m_fire1[ offs+m_cxBuf-1 ]+m_fire1[ offs+m_cxBuf ]+m_fire1[ offs+m_cxBuf+1 ]
				+m_fire1[offs+2*m_cxBuf-1]+m_fire1[offs+2*m_cxBuf]+m_fire1[offs+2*m_cxBuf+1]
				)>>3;

			if (col>0) --col;
			m_fire2[offs]=col;
			++offs;
		}
		m_fire2[offs]=0;
		++offs;
	}
}